var Map = require('js/map').Map;
var Match = require('js/match').Match;
var Enemy = require('js/enemy').Enemy;
var Obstacle = require('js/obstacle').Obstacle;
var Radar = require('js/radar').Radar;
var Turret = require('js/turret').Turret;
var GameEntity = require('js/gameEntity').GameEntity;
var gamejs = require('gamejs');

qModule('js/map', {
	setup: function() {
		this.testMatch = new Match();
		this.SIZE_SMALL = Obstacle.prototype.SIZE_SMALL;
		this.width = 100;
		this.height = 100;
		this.testMap = new Map(this.testMatch, this.width, this.height);
		this.testMatch.setMap(this.testMap);
	}
});


test("Map(Match, width, height)", function( ) {
	// Normal map
	ok(this.testMap instanceof Map,
		"Map created with argument: (Match, 100, 100)"
	);
});

test("Map.getMatch()", function( ) {
	// normal Match
	strictEqual(this.testMap.getMatch(), this.testMatch,
		"Test with a regular Match Object"
	);
});

test("Map.getSize()", function( ) {
	equal(this.testMap.getSize()[0], this.width, "Test #1 with a normal Map");
	equal(this.testMap.getSize()[1], this.height, "Test #2 with a normal Map");
	var size = [this.width, this.height];
	deepEqual(this.testMap.getSize(), size, "Test #3 with a normal Map");
});

test("Map.isInRange(coordinates)", function() {
	// An point inside the map
	ok(this.testMap.isInRange([10, 10]),
		"Test with argument: (point inside the Map)"
	);
	// Test on a point outside the map
	ok(!this.testMap.isInRange([this.width+10,this.height+10]),
		"Test with argument: (point outside the Map)"
	);
	// Test on top left angle
	ok(this.testMap.isInRange([0, 0]), "Test with argument: ([0, 0])");
	// Test on top right angle
	ok(this.testMap.isInRange([this.width-1, 0]),
		"Test with argument: ([width-1, 0])"
	);
	// Test on bottom right angle
	ok(this.testMap.isInRange([this.width-1, this.height-1]),
		"Test with argument: ([width-1, height-1])"
	);
	// Test on bottom left angle
	ok(this.testMap.isInRange([0, this.height-1]),
		"Test with argument: ([0, height-1])"
	);
	// Tests on some point just outside the top left angle
	ok(!this.testMap.isInRange([-1, 0]), "Test with argument: ([-1, 0])");
	ok(!this.testMap.isInRange([-1, -1]), "Test with argument: ([-1, -1])");
	ok(!this.testMap.isInRange([0, -1]), "Test with argument: ([0, -1])");
	// Tests on some point just outside the top right angle
	ok(!this.testMap.isInRange([this.width-1, -1]),
		"Test with argument: ([width-1, -1])"
	);
	ok(!this.testMap.isInRange([this.width, -1]),
		"Test with argument: ([width, -1])"
	);
	ok(!this.testMap.isInRange([this.width, 0]),
		"Test with argument: ([width, 0])"
	);
	// Tests on some point just outside the bottom right angle
	ok(!this.testMap.isInRange([this.width, this.height-1]),
		"Test with argument: ([width, height-1])"
	);
	ok(!this.testMap.isInRange([this.width, this.height]),
		"Test with argument: ([width, height])"
	);
	ok(!this.testMap.isInRange([this.width-1, this.height]),
		"Test with argument: ([width-1, height])"
	);
	// Tests on some point just outside the bottom left angle
	ok(!this.testMap.isInRange([0, this.height]),
		"Test with argument: ([0, height])"
	);
	ok(!this.testMap.isInRange([-1, this.height]),
		"Test with argument: ([-1, height])"
	);
	ok(!this.testMap.isInRange([-1, this.height-1]),
		"Test with argument: ([-1, height-1])"
	);
	// Tests that throws the right exception when using invalid coordinates
	throws(
		function() { this.testMap.isInRage([1, NaN]); },
		TypeError,
		"Test with argument: ([1, NaN])"
	);
	throws(
		function() { this.testMap.isInRage([NaN, 1]); },
		TypeError,
		"Test with argument: ([NaN, 1])"
	);
	throws(
		function() { this.testMap.isInRage([NaN, NaN]); },
		TypeError,
		"Test with argument: ([NaN, NaN])"
	);
});

test("Map.fill(coordinates)", function() {
	//invalid format coordinates
	throws(
		function() { this.testMap.fill([null, undefined]); },
		TypeError,
		"Test with argument: ([null, undefined])"
	);
	//out of range coordinates
	throws(
		function() { this.testMap.fill([-10, -5]); },
		RangeError,
		"Test with argument: ([-10, -5])"
	);
	throws(
		function() { this.testMap.fill([this.width, this.height]); },
		RangeError,
		"Test with argument: ([width, height])"
	);
	var cords = [4,4];
	// Test on empty coordinates (a new Map is totally empty)
	this.testMap.fill(cords);
	ok(this.testMap.isFilled(cords),
		"Test with argument: (empty cordinates)"
	);
	// Test on already filled coordinates
	this.testMap.fill(cords);
	ok(this.testMap.isFilled(cords),
		"Test with argument: (filled cordinates)"
	);
	// Test on cleared coordinates
	this.testMap.clear(cords);
	this.testMap.fill(cords);
	ok(this.testMap.isFilled(cords),
		"Test with argument: (cleared cordinates)"
	);
});

test("Map.clear(coordinates)", function( ) {
	//invalid format coordinates
	throws(
		function() { this.testMap.clear([null, undefined]); },
		TypeError,
		"Test with argument: ([null, undefined])"
	);
	//out of range coordinates
	throws(
		function() { this.testMap.clear([-10, -5]); },
		RangeError,
		"Test with argument: ([-10, -5])"
	);
	throws(
			function() { this.testMap.clear([this.width, this.height]); },
			RangeError,
			"Test with argument: ([100, 100])"
	);
	var cords = [4,4];
	// Test on empty cordinates (a new Map is totally empty)
	this.testMap.clear(cords);
	ok( ! this.testMap.isFilled(cords),
		"Test with argument: (empty cordinates)"
	);
	// Test on already cleared coordinates
	this.testMap.clear(cords);
	ok( ! this.testMap.isFilled(cords),
		"Test with argument: (cleared cordinates)"
	);
	// Test on filled coordinates
	this.testMap.fill(cords);
	this.testMap.clear(cords);
	ok( ! this.testMap.isFilled(cords),
		"Test with argument: (filled cordinates)"
	);
});

test("Map.isFilled(coordinates)", function( ) {
	throws(
			function() { this.testMap.isFilled([null, undefined]); },
			TypeError,
			"Test with argument: ([null, undefined])"
		);
		//out of range coordinates
		throws(
			function() { this.testMap.isFilled([-10, -5]); },
			RangeError,
			"Test with argument: ([-10, -5])"
		);
		throws(
				function() { this.testMap.isFilled([this.width, this.height]); },
				RangeError,
				"Test with argument: ([100, 100])"
		);
	var cords = [4,4];
	// Test on empty cordinates (a new Map is totally empty)
	ok( ! this.testMap.isFilled(cords),
		"Test with argument: (empty cordinates)"
	);
	// Test on cleared coordinates
	this.testMap.clear(cords);
	ok( ! this.testMap.isFilled(cords),
		"Test with argument: (cleared cordinates)"
	);
	// Test on filled coordinates
	this.testMap.fill(cords);
	ok(this.testMap.isFilled(cords),
		"Test with argument: (filled cordinates)"
	);
});

test("Map.addGameEntity(gameEntity)", function() {
	var testGE, cleared, filled, points, i;  // Hoisted variables
	var upgradeList = [ {image: IMAGE_ROOT + 'enemy.png'} ];
	var center = [100, 100];
	// GameEntity that doesn't fill the Map
	testGE = new GameEntity(this.testMatch, center, upgradeList, false);
	this.testMap.addGameEntity(testGE);  // This should do nothing
	cleared = true;  // Check that none of testGE points were filled
	points = testGE.pointsOnMap();
	for( i = 0; i < points.length; i ++) {
		cleared = cleared && ! this.testMap.isFilled(points[i]);
	}
	ok(cleared,
		"Test with argument (GameEntity with fillsMap = false): " +
		"nothing correctly done."
	);
	// GameEntity that fill the Map
	testGE = new GameEntity(this.testMatch, center, upgradeList, true);
	this.testMap.addGameEntity(testGE);
	ok(this.testMap._fillingEntities.has(testGE),
		"Test with argument (GameEntity with fillsMap = true): " +
		"GameEntity correctly added."
	);
	filled = true;  // Check that all the points were correctly filled
	points = testGE.pointsOnMap();
	for( i = 0; i < points.length; i ++) {
		filled = filled && this.testMap.isFilled(points[i]);
	}
	ok(filled,
		"Test with argument (GameEntity with fillsMap = true): " +
		"points on map correctly filled."
	);
});

test("Map.removeGameEntity(gameEntity)", function() {
	var testGE, cleared, points, i;  // Hoisted variables
	var upgradeList = [ {image: IMAGE_ROOT + 'enemy.png'} ];
	var center = [100, 100];
	// Test removing a GameEntity previously added
	testGE = new GameEntity(this.testMatch, center, upgradeList, true);
	this.testMap.addGameEntity(testGE);
	this.testMap.removeGameEntity(testGE);
	ok( ! this.testMap._fillingEntities.has(testGE),
		"Test with argument (GameEntity): "+
		"correctly removed from the group."
	);
	// Check that all the points on Map were cleared
	cleared = true;
	points = testGE.pointsOnMap();
	for( i = 0; i < points.length; i ++) {
		cleared = cleared && ! this.testMap.isFilled(points[i]);
	}
	ok(cleared,
		"Test with argument (GameEntity): " +
		"all points correctly cleared."
	);
	// Check that an exception is throwed if we remove a GameEntity
	// not on map
	throws(
		function() { this.testMap.removeGameEntity(testGE); },
		Error,
		"Test with argument (GameEntity not in Map): " +
		"exception correctly throwed."
	);
});

test("Map.getEnemies()", function(){
	var testEnemy = new Enemy(this.testMatch, [0,0]);
	this.testMap.addEnemy(testEnemy);
	var group = this.testMap.getEnemies();
	ok(group.has(testEnemy), "Test with argument: (Enemy)");
});

test("Map.addEnemy(enemy)", function(){
	var testEnemy = new Enemy(this.testMatch, [0,0]);
	this.testMap.addEnemy(testEnemy);
	var group = this.testMap.getEnemies();
	ok(group.has(testEnemy), "Test with argument: (Enemy)");
});

test("Map.removeEnemy(enemy)", function(){
	var testEnemy = new Enemy(this.testMatch, [0,0]);
	this.testMap.addEnemy(testEnemy);
	var found = this.testMap.getEnemies().has(testEnemy);
	this.testMap.removeEnemy(testEnemy);
	var notFound = this.testMap.getEnemies().has(testEnemy);
	ok( found != notFound, "Test with argument: (Enemy)");
});

test("Map.getObstacles()", function(){
	var testObstacle = new Obstacle(this.testMatch, [100,100]);
	this.testMap.addObstacle(testObstacle);
	var group = this.testMap.getObstacles();
	ok(group.has(testObstacle), "Test with argument: (Obstacle)");
});

test("Map.addObstacle(obstacle)", function(){
	var testObstacle = new Obstacle(this.testMatch, [100,100]);
	this.testMap.addObstacle(testObstacle);
	var group = this.testMap.getObstacles();
	ok(group.has(testObstacle), "Test with argument: (Obstacle)");
	// Test on matrix
	var check = true;
	var obstaclePoints = testObstacle.pointsOnMap();
	for(var i = 0; i < obstaclePoints.length; i++) {
		check = check && this.testMap.isFilled(obstaclePoints[i]);
	}
	ok(check, "Test with argument (Obstacle): all points correctly filled");
	// Already filled
	var anotherObstacle = new Obstacle(this.testMatch, [110, 110]);
	throws(
		function(){ this.testMap.addObstacle(anotherObstacle); },
		Error,
		"Test with argument (Obstacle in already filled space): " +
		"exception correctly throwed."
	);
});

test("Map.removeObstacle(obstacle)", function(){
	var testObstacle = new Obstacle(this.testMatch, [100,100]);
	this.testMap.addObstacle(testObstacle);
	var found = this.testMap.getObstacles().has(testObstacle);
	this.testMap.removeObstacle(testObstacle);
	var notFound = this.testMap.getObstacles().has(testObstacle);
	ok( found != notFound, "Test with argument: (Obstacle)");
	// Test on matrix
	var check = true;
	var obstaclePoints = testObstacle.pointsOnMap();
	for(var i = 0; i < obstaclePoints.length; i++) {
		check = check && ! this.testMap.isFilled(obstaclePoints[i]);
	}
	ok(check, "Test with argument (Obstacle): all points correctly cleared");
	// Obstacle not in the group
	var anotherObstacle = new Obstacle(this.testMatch, [200,200]);
	throws(
		function(){ this.testMap.removeObstacle(anotherObstacle); },
		Error,
		"Test with argument: (Obstacle not on Map)"
	);
});

test("Map.getRadars()", function(){
	var testRadar = new Radar(this.testMatch, [10, 10]);
	this.testMap.addRadar(testRadar);
	var group = this.testMap.getRadars();
	ok(group.has(testRadar), "Test with argument: (Radar)");
});

test("Map.addRadar(radar)", function(){
	var testRadar = new Radar(this.testMatch, [100, 100]);
	this.testMap.addRadar(testRadar);
	var group = this.testMap.getRadars();
	ok(group.has(testRadar), "Test with argument: (Radar)");
	// Test on matrix
	var check = true;
	var radarPoints = testRadar.pointsOnMap();
	for(var i = 0; i < radarPoints.length; i++) {
		check = check && this.testMap.isFilled(radarPoints[i]);
	}
	ok(check, "Test with argument (Radar): all points correctly filled");
	// Already filled
	var anotherRadar = new Radar(this.testMatch, [101, 101]);
	throws(
		function(){ this.testMap.addRadar(anotherRadar); },
		Error,
		"Test with argument: (Radar in already filled space): " +
		"exception correctly throwed."
	);
});

test("Map.removeRadar(radar)", function(){
	var testRadar = new Radar(this.testMatch, [100, 100]);
	this.testMap.addRadar(testRadar);
	var found = this.testMap.getRadars().has(testRadar);
	this.testMap.removeRadar(testRadar);
	var notFound = this.testMap.getRadars().has(testRadar);
	ok( found != notFound, "Test with argument: (Radar)");
	// Test on matrix
	var check=true;
	var radarPoints = testRadar.pointsOnMap();
	for(var i = 0; i < radarPoints.length; i++) {
		check = check && ! this.testMap.isFilled(radarPoints[i]);
	}
	ok(check, "Test with argument (Radar): points correctly cleared.");
	// Radar not in the group
	var anotherRadar = new Radar(this.testMap.testMatch, [200,200]);
	throws(
		function(){ this.testMap.removeRadar(anotherRadar); },
		Error,
		"Test with argument (Radar not on Map): " +
		"exception correctly throwed."
	);
});

test("Map.getTurrets()", function(){
	var testTurret = new Turret(this.testMatch, [100,100]);
	this.testMap.addTurret(testTurret);
	var group = this.testMap.getTurrets();
	ok(group.has(testTurret), "Test works with a Turret");
});

test("Map.addTurret(turret)", function(){
	var testTurret = new Turret(this.testMatch, [100,100]);
	this.testMap.addTurret(testTurret);
	var group = this.testMap.getTurrets();
	ok(group.has(testTurret), "Test with argument: (Turret)");
	// Check if all turret pixels are correctly filled
	var turretPoints = testTurret.pointsOnMap();
	var filled = true;
	for (var i = 0; i < turretPoints.length; i++) {
		filled = filled && this.testMap.isFilled(turretPoints[i]);
	}
	ok(filled, "Test with argument: (Turret): all points correctly filled");
	// Check that the exception is throwed when adding a turret on a
	// filled space
	var testTurret2 = new Turret(this.testMatch, [100,100]);
	throws(
		function() { this.testMap.addTurret(testTurret2); },
		Error,
		"Test with argument: (Turret on an already filled space)"
	);
});

test("Map.removeTurret(turret)", function(){
	var testTurret = new Turret(this.testMatch, [100,100]);
	// Check that the turret is correctly removed from the turrets Group
	this.testMap.addTurret(testTurret);
	this.testMap.removeTurret(testTurret);
	var found = this.testMap.getTurrets().has(testTurret);
	ok(!found, "Test with argument: (Turrent on Map)");
	// Check if all turret pixels are correctly cleared
	var turretPoints = testTurret.pointsOnMap();
	var filled = false;
	for (var i = 0; i < turretPoints.length; i++) {
		filled = filled || this.testMap.isFilled(turretPoints[i]);
	}
	ok(!filled, "Test with argument (Turrent on Map): points cleared");
	// Check that the exception is throwed when removing a turrent not on the
	// map
	throws(
		function() { this.testMap.removeTurret(testTurret); },
		Error,
		"Test with argument (Turrent not on Map): " +
		"exception correctly throwed."
	);
});